#include "vObject.h"
#include "SurfManager.h"
#include "RingManager.h"
#include "TerrainManager.h"

//============================================
//	PLANET class
//	*	Atmosphere
//	*	Rings
//	*	Bases
//	*	Clouds
//============================================

class SurfBaseDataParser: public ConfigFileBlockParser
{
private:
	struct DirEntry
	{
		std::string dir;
	};
	StringVector m_preBases;
	StringVector m_directories;
	StringVector m_postBases;
	bool m_containDirectoryEntries;
public:
	SurfBaseDataParser(const StringVector& src)
		: ConfigFileBlockParser(src)
	{
		m_containDirectoryEntries = false;
		size_t size = m_src.size();
		for(size_t i = 0; i < size; i++)
		{
			auto val = PeekFirstStringValue(m_src[i]);
			if (val == "DIR")
			{
				//directory
				m_containDirectoryEntries = true;
				std::string dir;
				ParseLine(m_src[i], "S03S00", dir, 1);
				m_directories.push_back(dir);
			}
			else if (val.length() > 1 && val[val.length() - 1] == ':')
			{
				//"inline" base
				if (m_containDirectoryEntries)
				{
					// "post-base"
					m_postBases.push_back(val.substr(0, val.length() - 1));
				}
				else
				{
					// "pre-base"
					m_preBases.push_back(val.substr(0, val.length() - 1));
				}
			}
		}
	}

	const StringVector& GetDirectories() const { return m_directories; }
	const StringVector& GetPreBases() const { return m_preBases; }
	const StringVector& GetPostBases() const { return m_postBases; }
	bool ContainDirectories() const { return m_containDirectoryEntries; }
};

struct ParserItem
{
	const ConfigFileParser * parser;
	bool used;
};

vPlanet::vPlanet( OBJHANDLE _obj, Scene * scene ) : vObject( _obj, scene ) {
//name, label
//basic
	Base = NULL;
	PlanetMesh = NULL;
	TerM = NULL;
	AtmM = NULL;
	RingData = NULL;
	CloudData = NULL;

	ReadPlanetaryConfig();

	nbase = 0;
	rad = (float)oapiGetSize( obj );
	render_rad = (float)(rad*0.1);
	dist_scale = 1.0f;
	patchres = 0;
	ring_res = 0;
	Mass = oapiGetMass( obj );

	//==================================================
	//					Atmosphere
	//==================================================

	AtmM = new AtmosphereMgr( this );
	CFG.AtmosphereType = 0;

	//==================================================
	//					Terrain/Mesh
	//==================================================

	if( *(int*)gc->GetConfigParam( CFGPRM_SURFACEMAXLEVEL ) != 0 && *(int*)oapiGetObjectParam( obj, OBJPRM_PLANET_SURFACEMAXLEVEL ) )
	{
		oapiWriteLogV("Initializing terrain...");
		TerM = new TerrainManager( obj, this );
	}
	else {
		MESHHANDLE hMesh = oapiLoadMesh( name );
		if( hMesh ) {
			PlanetMesh = new D3D11Mesh( hMesh, false );
			oapiDeleteMesh( hMesh );
		}
	}

	bRipple = *(bool*)gc->GetConfigParam( CFGPRM_SURFACERIPPLE ) && *(bool*)oapiGetObjectParam( obj, OBJPRM_PLANET_SURFACERIPPLE );
	if( bRipple )
		TerM->SetWaterMicroTexture( "waves.dds" );
	shadow_alpha = (float)(*(double*)oapiGetObjectParam( obj, OBJPRM_PLANET_SHADOWCOLOUR ));
	bVesselShadows = *(bool*)gc->GetConfigParam( CFGPRM_VESSELSHADOWS ) && shadow_alpha >= 0.01;

//add clouds if planet has clouds
	if( *(bool*)gc->GetConfigParam( CFGPRM_CLOUDS ) && *(bool*)oapiGetObjectParam( obj, OBJPRM_PLANET_HASCLOUDS ) ) {
		CloudData = new CLOUDPRM;
		memset( CloudData, 0, sizeof(CLOUDPRM) );
		CloudData->CM = new CloudManager( this );			//cloud manager class
		CloudData->cloud_rad = rad + *(double*)oapiGetObjectParam( obj, OBJPRM_PLANET_CLOUDALT );
		CloudData->bCloudShadow = *(bool*)gc->GetConfigParam( CFGPRM_CLOUDSHADOWS );
		if( CloudData->bCloudShadow ) {
			CloudData->fShadowAlpha = 1.0f - *(float*)oapiGetObjectParam( obj, OBJPRM_PLANET_CLOUDSHADOWCOL );
			if( CloudData->fShadowAlpha < 0.01f )
				CloudData->bCloudShadow = false;
		}
		if( *(bool*)oapiGetObjectParam( obj, OBJPRM_PLANET_CLOUDMICROTEX ) ) {
			CloudData->CM->SetMicrotexture( "cloud1.dds" );
			CloudData->micro_alt0 = *(double*)oapiGetObjectParam( obj, OBJPRM_PLANET_CLOUDMICROALTMIN );
			CloudData->micro_alt1 = *(double*)oapiGetObjectParam( obj, OBJPRM_PLANET_CLOUDMICROALTMAX );
		}
	}

//add rings if planet has rings
 	if( *(bool*)oapiGetObjectParam( obj, OBJPRM_PLANET_HASRINGS ) ) {
		RingData = new RINGPRM;
		memset( RingData, 0, sizeof(RINGPRM) );
		RingData->minrad = *(double*)oapiGetObjectParam( obj, OBJPRM_PLANET_RINGMINRAD );
		RingData->maxrad = *(double*)oapiGetObjectParam( obj, OBJPRM_PLANET_RINGMAXRAD );
		RingData->RM = new RingManager( this, RingData->minrad, RingData->maxrad );
		render_rad = (float)(rad*RingData->maxrad);
	}

//Fog and atm consts
	memcpy( &Fog, oapiGetObjectParam( obj, OBJPRM_PLANET_FOGPARAM ), sizeof(FogParam) );
	if( *(bool*)gc->GetConfigParam( CFGPRM_ATMFOG ) && Fog.dens_0 > 0 )		bFog = true;
	else																	bFog = false;
	atmc = oapiGetPlanetAtmConstants( obj );

	nbase = oapiGetBaseCount( obj );
	if( nbase ) 
	{
		char configName[200];
		size_t i = 0;
		sprintf(configName, "Config\\%s.cfg", name);
		const ConfigFileParser * parser = ConfigFileParser::ParseFile(configName);

		StringVector baseSearchDirs;
		auto blockParser = parser->GetBlock<SurfBaseDataParser>("SURFBASE");
		if (blockParser == nullptr || !blockParser->ContainDirectories())
		{
			std::string stdDir;
			stdDir += "Config\\";
			stdDir += name;
			stdDir += "\\Base";
			baseSearchDirs.push_back(stdDir);
		}
		else
		{
			auto dirs = blockParser->GetDirectories();
			for(i = 0; i < dirs.size(); i++)
			{
				baseSearchDirs.push_back("Config\\" + dirs[i]);
			}
		}
		delete parser;
		//now scan all base config directories and load all config, looking up base name
		std::map<std::string, ParserItem> foundConfigFiles;

		auto probeBase = [&](const std::string& fileName) 
		{
			if (::GetFileAttributes(fileName.c_str()) != INVALID_FILE_ATTRIBUTES)
			{
				//file exists, load it
				auto baseFileParser = ConfigFileParser::ParseFile(fileName.c_str());
				std::string baseName;
				if (baseFileParser->GetStringProperty("Name", &baseName))
				{
					//name found, add to the map (if not there yet)
					if (foundConfigFiles.count(baseName) == 0)
					{
						ParserItem item;
						item.parser = baseFileParser;
						item.used = false;
						foundConfigFiles.insert(std::make_pair(baseName, item));
					}
					else
					{
						//config for that base has already been loaded, so now just delete base parser
						delete baseFileParser;
					}
				}
				else
				{
					//something went wrong, just delete base parser
					delete baseFileParser;
				}
			}
		};

		if (blockParser)
		{
			auto inlineBases = blockParser->GetPreBases();
			for(i = 0; i < inlineBases.size(); i++)
			{
				std::string baseFName = "Config\\" + inlineBases[i] + ".cfg";
				probeBase(baseFName);
			}
		}
		for(i = 0; i < baseSearchDirs.size(); i++)
		{
			WIN32_FIND_DATA findData;
			std::string searchPath = baseSearchDirs[i] + "\\*.cfg";
			HANDLE hSearch = ::FindFirstFileA(searchPath.c_str(), &findData);
			if (hSearch)
			{
				do
				{
					std::string fileName = baseSearchDirs[i] + "\\" + findData.cFileName;
					probeBase(fileName);
				}
				while(::FindNextFileA(hSearch, &findData));
				::FindClose(hSearch);
			}
		}
		if (blockParser)
		{
			auto inlineBases = blockParser->GetPostBases();
			for(i = 0; i < inlineBases.size(); i++)
			{
				std::string baseFName = "Config\\" + inlineBases[i] + ".cfg";
				probeBase(baseFName);
			}
			delete blockParser;
		}
		
		Base = new vBase *[nbase];
		for( DWORD j = 0; j < nbase; j++ )
		{
			auto baseHandle = oapiGetBaseByIndex( obj, j );
			char objectName[200];
			oapiGetObjectName(baseHandle, objectName, 200);
			std::string baseName(objectName);
			ParserItem& parser = foundConfigFiles[baseName];
			Base[j] = new vBase( this, obj, baseHandle, parser.parser, GetScene() );
			parser.used = true;
		}
		//delete parsers that were not used to prevent memory leak
		StringVector itemsToDelete;
		for(auto it = foundConfigFiles.begin(); it != foundConfigFiles.end(); it++)
		{
			auto item = *it;
			if (!item.second.used)
			{
				itemsToDelete.push_back(item.first);
			}
		}
		for(auto it = itemsToDelete.begin(); it != itemsToDelete.end(); it++)
		{
			foundConfigFiles.erase(*it);
		}
		itemsToDelete.clear();
	}

//init constants
	DWORD dAmbient = *(DWORD*)gc->GetConfigParam( CFGPRM_AMBIENTLEVEL );
	AP.GlobalAmb = ((float)dAmbient)*0.0039f;
	if( atmc ) {
		AP.Ambient0 = min( 0.7f, log( (float)atmc->rho0 + 1.0f)*0.4f );
		AP.Dispersion = max( 0.02f, min( 0.9f, log( (float)atmc->rho0 + 1.0f ) ) );
	}
	else {
		AP.Ambient0 = 0.0f;
		AP.Dispersion = 0.0f;
	}

	//base craters:
	BaseCraters = NULL;
	if( nbase ) {
		double rad;
		BaseCraters = new BaseCraterParams [nbase];
		for( int j = 0; j < nbase; j++ ) {
			double radius;
			//oapiGetGlobalPos( Base[j]->obj, &GPos );//get global pos of base
			oapiGetBaseEquPos( Base[j]->obj, &BaseCraters[j].lng, &BaseCraters[j].lat, &radius );//get latitude and longtitude of base

			BaseCraters[j].lng = normangle(BaseCraters[j].lng);
			BaseCraters[j].lat = normangle(BaseCraters[j].lat);
			//get correct craters dimensions:
			rad = Base[j]->GetBaseMeshRadius();
			BaseCraters[j].minrad = rad > 5000.0 ? rad*1.2 : 5000.0;//bases without meshes shouldn't have craters with 0.0 radius
			BaseCraters[j].maxrad = BaseCraters[j].minrad*2.0f;

			//meters => RAD
			//rad = rad*PI2;
			BaseCraters[j].minrad /= radius;
			BaseCraters[j].maxrad /= radius;
		}
	}
}

vPlanet::~vPlanet() {
	if( PlanetMesh )	delete PlanetMesh;
	if( TerM )	delete TerM;
	if( AtmM ) {
		switch( AtmM ->AtmType ) {
		case NO_ATMOSPHERE:
			delete AtmM;
			break;
		}
	}
	if( CloudData ) {
		delete CloudData->CM;
		delete CloudData;
	}
	if( RingData ) {
		delete RingData->RM;
		delete RingData;
	}
	if( nbase ) {
		for( DWORD j = 0; j < nbase; j++ )
			delete Base[j];
		delete [ ] Base;
	}
	//base craters:
	if( BaseCraters )	delete [ ] BaseCraters;
}

//================================================
//			Update
//================================================

void vPlanet::Update() {
	DWORD i, j;

	vObject::SaveData();	//not releveant in the case of a planet

	if( !active )	return;

	vObject::Update();

	if (TerM)
	{
		TerM->UnloadTextures();
	}

	if( nbase )
		for( j = 0; j < nbase; j++ )
			Base[j]->Update();

	if( patchres == 0 )		//nothing to do
		return;

	float rad_scale = rad;
	bool rescale = false;
	dist_scale = 1.0f;

	const static double farplane = 1e6;			//?
	const static double render_rad = 0.1*rad;	//?
//change scale if needed
	if( CDist+render_rad > farplane && CDist-rad > 1e4 ) {
		rescale = true;
		dist_scale = (float)(farplane/(CDist+render_rad));
	}
//rescale
	if( rescale ) {
		rad_scale *= dist_scale;
		mWorld._41 *= dist_scale;
		mWorld._42 *= dist_scale;
		mWorld._43 *= dist_scale;
	}
//scale world matrix	
	mWorld._11 *= rad_scale; mWorld._12 *= rad_scale; mWorld._13 *= rad_scale;
	mWorld._21 *= rad_scale; mWorld._22 *= rad_scale; mWorld._23 *= rad_scale;
	mWorld._31 *= rad_scale; mWorld._32 *= rad_scale; mWorld._33 *= rad_scale;
//clouds
	if( CloudData ) {
		CloudData->RenderMode = ( CDist < CloudData->cloud_rad ? 1 : 0 );
		if( CDist > CloudData->cloud_rad*(1.0-1.5e-4) )	//camera can be "in" the cloud layer
			CloudData->RenderMode |= 2;
		if( CloudData->RenderMode & 1 ) {
			CloudData->viewap = acos( rad/CloudData->cloud_rad );
			if( rad < CDist )
				CloudData->viewap += acos( rad/CDist );
		}
		else
			CloudData->viewap = 0.0;

		float cloud_scale = (float)(CloudData->cloud_rad/rad);
		double cloud_rot = *(double*)oapiGetObjectParam( obj, OBJPRM_PLANET_CLOUDROTATION );

		//world matrix for cloud shadows
		memcpy( &CloudData->mW_cloudshadow, &mWorld, 64 );

		if( cloud_rot ) {
			static D3DXMATRIX crot(	1, 0, 0, 0,
									0, 1, 0, 0,
									0, 0, 1, 0,
									0, 0, 0, 1	);
			crot._11 = crot._33 = (float)cos( cloud_rot );
			crot._13 = -(crot._31 = (float)sin( cloud_rot ) );
			D3DXMatrixMultiply( &CloudData->mW_cloudshadow, &crot, &CloudData->mW_cloudshadow );
		}

		//world matrix for clouds
		memcpy( &CloudData->mW_cloud, &CloudData->mW_cloudshadow, 64 );

		for( i = 0; i < 3; i++ )
			for( j = 0; j < 3; j++ )
				CloudData->mW_cloud.m[i][j] *= cloud_scale;		//cloud should be above ground and above shadows

		//set microtexture intensity
		double alt = CDist - rad;
		double lvl = (CloudData->micro_alt1 - alt)/(CloudData->micro_alt1 - CloudData->micro_alt0);
		CloudData->CM->SetMicrolevel( max( 0.0, min( 1.0, lvl ) ) );
	}

	//base visuals
	//no bases so far
	InitLegacyAtmosphere();
	
	if( TerM )
		TerM->SaveParams( &mWorld, dist_scale, patchres, 0.0, bFog );

//	if( SM )
//		SM->SaveParams( &mWorld, dist_scale, patchres, 0.0, bFog );

	if( AtmM )
		AtmM->SaveParams();
	if( RingData )	RingData->RM->SaveParams();
	if( CloudData ) {
		CloudData->CM->SaveParams( &CloudData->mW_cloud, dist_scale, min( patchres, 8 ), CloudData->viewap );
		CloudData->CM->SaveParamsShadows( &CloudData->mW_cloudshadow, dist_scale, min( patchres, 8 ), CloudData->viewap, CloudData->fShadowAlpha );
	}
}

void vPlanet::CheckResolution() {
	double alt = max( 1.0, CDist - rad );
	double apr = rad * cfg->cHeight*0.5 / ( alt*tan( oapiCameraAperture()/*SC->GetCamAperture()*/ ));

	int new_patchres;
	double ntx;

	if( apr < 2.5 ) {
		new_patchres = 0;
		ntx = 0;
	}
	else {
		static const double scal2 = 1.0/log(2.0);

		ntx = PI2 * apr;		
		new_patchres = min( max( (int)(scal2*log( ntx ) - 5.0), 1 ), MAX_RESOLUTION );
	}

	if( new_patchres != patchres ) {
		if( RingData )
			RingData->res = ( new_patchres <= 3 ? 0 : (new_patchres <= 4 ? 1 : 2 ));
		patchres = new_patchres;
	}
}

//================================================
//			Render
//================================================

void vPlanet::Render() 
{
#if _DEBUG
	wchar_t message[200];
	std::wstring vesselName = string2wstring(name);
	wsprintfW(message, L"[%s]vPlanet::Render()", vesselName.c_str());
	D3DPERF_BeginEvent(D3DCOLOR_ARGB(0xFF, 0, 0x40, 0x40), message);
#endif
	DWORD j;

	if( !active )		return;
	if( patchres == 0 ) return;

	bool fog = bFog;

	DWORD bg_color = SC->GetBgColor();
	bool add_ambient = ((bg_color & 0xFFFFFF) && (obj != SC->GetProxyBody()));

	if( RingData )									
	{
#if _DEBUG
		D3DPERF_BeginEvent(D3DCOLOR_ARGB(0xFF, 0, 0x40, 0x40), L"render planetary rings");
#endif
		//render planetary rings
		RingData->RM->Render( &mWorld, RingData->res, false );
#if _DEBUG
		D3DPERF_EndEvent();
#endif
	}
			
	if( AtmM )
	{
#if _DEBUG
		D3DPERF_BeginEvent(D3DCOLOR_ARGB(0xFF, 0, 0x40, 0x40), L"render atmosphere haze - RenderBeforeTerrain");
#endif
		//render atmosphere haze
		AtmM->RenderBeforeTerrain( &mWorld, dist_scale, false );
#if _DEBUG
		D3DPERF_EndEvent();
#endif
	}

	if( CloudData && (CloudData->RenderMode & 1) )
	{
#if _DEBUG
		D3DPERF_BeginEvent(D3DCOLOR_ARGB(0xFF, 0, 0x40, 0x40), L"render clouds from below");
#endif
		//render clouds from below
		CloudData->CM->Render( false );
#if _DEBUG
		D3DPERF_EndEvent();
#endif
	}

	if( bFog /*&& obj == SC->GetProxyBody()*/ ) {
		fog = InitFogParams( fog );
		SC->SetAtmParams( &AP );
	}

	if( !fog )
		AP.HazeMode = 0;

	if( !PlanetMesh ) {
		if( bFog )		AP.FogDensity /= dist_scale;
	//	SM->Render();					//render planet surface		
		
#if _DEBUG
		D3DPERF_BeginEvent(D3DCOLOR_ARGB(0xFF, 0, 0x40, 0x40), L"render terrain");
#endif
		TerM->Render();
#if _DEBUG
		D3DPERF_EndEvent();
#endif

		if( nbase ) {
			if( bFog )		AP.FogDensity *= dist_scale;
			
#if _DEBUG
		D3DPERF_BeginEvent(D3DCOLOR_ARGB(0xFF, 0, 0x40, 0x40), L"render Base Tiles");
#endif
			D3D11Mesh::InitRenderBaseTiles();
			for( j = 0; j < nbase; j++ )		//render base tiles
				Base[j]->RenderSurface();
#if _DEBUG
		D3DPERF_EndEvent();
#endif
			
#if _DEBUG
		D3DPERF_BeginEvent(D3DCOLOR_ARGB(0xFF, 0, 0x40, 0x40), L"RenderStructuresBS");
#endif
			D3D11Mesh::InitRender();
			for( j = 0; j < nbase; j++ )		//render base structures before shadows
				Base[j]->RenderStructuresBS();
#if _DEBUG
		D3DPERF_EndEvent();
#endif
	/*		
#if _DEBUG
		D3DPERF_BeginEvent(D3DCOLOR_ARGB(0xFF, 0, 0x40, 0x40), L"RenderShadows");
#endif
			D3D11Mesh::InitRenderShadows();
			for( j = 0; j < nbase; j++ )		//render shadows on surface and strustures
				Base[j]->RenderShadows();
#if _DEBUG
		D3DPERF_EndEvent();
#endif
	*/		
#if _DEBUG
		D3DPERF_BeginEvent(D3DCOLOR_ARGB(0xFF, 0, 0x40, 0x40), L"RenderStructuresAS");
#endif
			D3D11Mesh::InitRender();
			for( j = 0; j < nbase; j++ )
				Base[j]->RenderStructuresAS();
#if _DEBUG
		D3DPERF_EndEvent();
#endif
			
#if _DEBUG
		D3DPERF_BeginEvent(D3DCOLOR_ARGB(0xFF, 0, 0x40, 0x40), L"render base lights");
#endif
			vBase::InitRenderBaseLights();
			for( j = 0; j < nbase; j++ )
				Base[j]->RenderBaseLights();
#if _DEBUG
		D3DPERF_EndEvent();
#endif
		}
	}
	else {
		D3D11Mesh::InitRender();

		def_SunLight.SunDir = AP.SunDir;
		memcpy( &D3D11Mesh::VSCB_per_object.Sun, &def_SunLight, 64 );
		D3D11Mesh::VSCB_per_object.VP = *SC->GetVP();
		D3D11Mesh::VSCB_per_object.ActiveLightsCount = 0;
		iCtx->UpdateSubresource( D3D11Mesh::cb_VS_per_object, 0, NULL, &D3D11Mesh::VSCB_per_object, 0, 0 );

		memcpy( &D3D11Mesh::PSCB_per_object.Sun, &def_SunLight, 64 );
		iCtx->UpdateSubresource( D3D11Mesh::cb_PS_per_object, 0, NULL, &D3D11Mesh::PSCB_per_object, 0, 0 );

		PlanetMesh->Render( this, &mWorld, NULL, false );
	}

	if( fog )
		AP.HazeMode = 0;

	iCtx->OMSetDepthStencilState( DSS_NoDepth_NoStencil, 0 );

	if( CloudData && CloudData->bCloudShadow )
	{
#if _DEBUG
		D3DPERF_BeginEvent(D3DCOLOR_ARGB(0xFF, 0, 0x40, 0x40), L"render cloud shadows on surface");
#endif
		//render cloud shadows on surface
		CloudData->CM->RenderShadow();
#if _DEBUG
		D3DPERF_EndEvent();
#endif
	}

	if( CloudData && (CloudData->RenderMode & 2) )
	{
#if _DEBUG
		D3DPERF_BeginEvent(D3DCOLOR_ARGB(0xFF, 0, 0x40, 0x40), L"render clouds from above");
#endif
		//render clouds from above
		CloudData->CM->Render( true );
#if _DEBUG
		D3DPERF_EndEvent();
#endif
	}

	if( AtmM )
	{
#if _DEBUG
		D3DPERF_BeginEvent(D3DCOLOR_ARGB(0xFF, 0, 0x40, 0x40), L"haze across surface - RenderAfterTerrain");
#endif
		//haze across surface
		AtmM->RenderAfterTerrain( &mWorld, dist_scale, true );
#if _DEBUG
		D3DPERF_EndEvent();
#endif
	}

	if( RingData )
	{
#if _DEBUG
		D3DPERF_BeginEvent(D3DCOLOR_ARGB(0xFF, 0, 0x40, 0x40), L"render planetary rings");
#endif
		RingData->RM->Render( &mWorld, RingData->res, true );
#if _DEBUG
		D3DPERF_EndEvent();
#endif
	}

	/*if( SC->GetProxyBody() == obj )
		SC->RenderVesselShadows( this );*/
#if _DEBUG
	D3DPERF_EndEvent();
#endif
}

void vPlanet::RenderShadowMap()
{
	//TODO draw terrain for shadow map
	if (nbase)
	{
		for(auto i = 0; i < nbase; i++)
		{
			if (Base[i]->GetCDist() < 20000)
			{
				Base[i]->RenderShadows();
			}
		}
	}

	//TerM->RenderTerrainShadows();
}

//atmosphere params
void vPlanet::InitLegacyAtmosphere() {
	VECTOR3 gpos = GPos;
	AP.SunAppRad = (float)( SunSize / length(pSun - gpos) );
	normalise( gpos );
	AP.SunDir.x = (float)gpos.x;
	AP.SunDir.y = (float)gpos.y;
	AP.SunDir.z = (float)gpos.z;
}

bool vPlanet::InitFogParams( bool bfog ) {
	float fogfactor = 0.0f;

	double h = max( 1.0, CDist - rad );	//cdist!

	VECTOR3 fogcol = Fog.col;
	double h_ref = Fog.alt_ref;
	double fog_0 = Fog.dens_0;
	double fog_ref = Fog.dens_ref;
	double scl = h_ref*fog_ref;

	if( h < h_ref )		fogfactor = (float)(h/h_ref*(fog_ref - fog_0) + fog_0 );
	else				fogfactor = (float)(scl/h);

	if( fogfactor < 0.0f )
		return false;
	else {
		double cosa = dotp( unit(GPos), unit( SC->GetCamPos() ) );	//cpos!
		double bright = 0.5*max( 0.0, min( 1.0, cosa + 0.3 ));

		AP.FogColor.r = (float)(bright*(min( 1.0, fogcol.x ) + 0.5 ) );
		AP.FogColor.g = (float)(bright*(min( 1.0, fogcol.y ) + 0.5 ) );
		AP.FogColor.b = (float)(bright*(min( 1.0, fogcol.z ) + 0.5 ) );
		AP.FogDensity = fogfactor;///dist_scale;
		AP.HazeMode = 2;
		return true;
	}
}

void vPlanet::ReadPlanetaryConfig() {

	memset( &CFG, 0, sizeof(PlanetaryConfig) );
	CFG.maxTileResolution = min( *(int*)gc->GetConfigParam( CFGPRM_SURFACEMAXLEVEL ), *(int*)oapiGetObjectParam( obj, OBJPRM_PLANET_SURFACEMAXLEVEL ) );
	CFG.bSpecularMap = true;
	CFG.TerrainCoarseMicroTex_mult = 1.0f;
	CFG.TerrainFlatMicroTex_mult = 1.0f;
	CFG.MicroTexTransAngles[0] = 0.0f;
	CFG.MicroTexTransAngles[1] = 1.57f;

	CoverTypeParam ctp;
	memset( &ctp, 0, sizeof(ctp) );

	FILE *file = NULL;
	char line[256], item[256];
	sprintf( line, "Config\\NGPlanetConfig\\%s_ng.cfg", name );
	if( !(file = fopen( line, "rb")) ) {
		CFG.CTP.push_back( ctp );
		return;
	}
	fclose( file );

	int ival;
	double fval;
	FILEHANDLE hFile = NULL;
	sprintf( line, "NGPlanetConfig\\%s_ng.cfg", name );
	hFile = oapiOpenFile( line, FILE_IN, CONFIG );
	if( oapiReadItem_int( hFile, "Max tile resolution level", ival ) )	CFG.maxTileResolution = ival;
	if( oapiReadItem_float( hFile, "Min tesselation factor", fval ) )	CFG.minEdgeTessFactor = (float)fval;
	if( oapiReadItem_float( hFile, "Max tesselation factor", fval ) )	CFG.maxEdgeTessFactor = (float)fval;

	if( oapiReadItem_float( hFile, "Max terrain elevation", fval ) )	CFG.maxTerrainElevation = (float)fval;
	if( oapiReadItem_float( hFile, "Max terrain altitude", fval ) )		CFG.maxTerrainAlt = (float)fval;
	if( oapiReadItem_float( hFile, "Min normal altitude", fval ) )		CFG.minNormalAlt = (float)fval;
	if( oapiReadItem_float( hFile, "Max microtex. distance", fval ) )	CFG.maxMicroTexDist = (float)fval;

	if( oapiReadItem_string( hFile, "Terrain microtex. transition angles", item ) )
		sscanf( item, "%f %f", &CFG.MicroTexTransAngles[0], &CFG.MicroTexTransAngles[1] );

	oapiReadItem_bool( hFile, "Use displacement microtextures", CFG.bDisplacementMicroTex );
	oapiReadItem_bool( hFile, "Use diffuse microtextures", CFG.bDiffuseMicroTex );
	oapiReadItem_bool( hFile, "Use normal microtextures", CFG.bNormalMicroTex );

	if( oapiReadItem_float( hFile, "Displacement microtex. range", fval ) )	CFG.DisplacementMicroTex_range = (float)fval;
	if( oapiReadItem_float( hFile, "Displacement microtex. mult", fval ) )	CFG.DisplacementMicroTex_mult = (float)fval;
	if( oapiReadItem_float( hFile, "Flat terrain microtex. mult", fval ) )	CFG.TerrainFlatMicroTex_mult = (float)fval;
	if( oapiReadItem_float( hFile, "Coarse terrain microtex. mult", fval ) )CFG.TerrainCoarseMicroTex_mult = (float)fval;
	if( oapiReadItem_float( hFile, "Low LOD terrain features microtex. mult", fval ) )	CFG.TerrainLowDetailsMicroTex_mult = (float)fval;
	
	bool enable = false;
	CFG.AtmosphereType = 1;
	if( oapiReadItem_bool( hFile, "Enable advanced atmosphere", enable ) && enable )	{
		//all these parameters MUST be defined:
		if( oapiReadItem_string( hFile, "betaR", item ) )		sscanf( item, "%f %f %f", &CFG.PASCfg.betaR.x, &CFG.PASCfg.betaR.y, &CFG.PASCfg.betaR.z ); 
		else													goto skip;

		if( oapiReadItem_string( hFile, "betaMSca", item ) )	sscanf( item, "%f %f %f", &CFG.PASCfg.betaMSca.x, &CFG.PASCfg.betaMSca.y, &CFG.PASCfg.betaMSca.z ); 
		else													goto skip;

		if( oapiReadItem_string( hFile, "betaMEx", item ) )		sscanf( item, "%f %f %f", &CFG.PASCfg.betaMEx.x, &CFG.PASCfg.betaMEx.y, &CFG.PASCfg.betaMEx.z ); 
		else													goto skip;

		if( oapiReadItem_float( hFile, "HR", fval ) )			CFG.PASCfg.HR = fval;
		else													goto skip;

		if( oapiReadItem_float( hFile, "HM", fval ) )			CFG.PASCfg.HM = fval;
		else													goto skip;

		if( oapiReadItem_float( hFile, "Rg", fval ) )			CFG.PASCfg.Rad_ground = fval;
		else													goto skip;

		if( oapiReadItem_float( hFile, "Rt", fval ) )			CFG.PASCfg.Rad_max = fval;
		else													goto skip;

		if( oapiReadItem_float( hFile, "RL", fval ) )			CFG.PASCfg.Rad_limit = fval;
		else													goto skip;

		if( oapiReadItem_float( hFile, "mieG", fval ) )			CFG.PASCfg.mieG = fval;
		else													goto skip;

		if( oapiReadItem_float( hFile, "AVG", fval ) )			CFG.PASCfg.AverageGroundReflectance = fval;
		else													goto skip;

		CFG.AtmosphereType = 2;
	}
skip:

	oapiReadItem_bool( hFile, "Enable advanced clouds", CFG.bAdvancedClouds );
	oapiReadItem_bool( hFile, "Enable particle rings", CFG.bParticleRings );
	oapiReadItem_bool( hFile, "Enable planetary ring shadows", CFG.bRingShadows );
	oapiReadItem_bool( hFile, "Enable planetary shadows", CFG.bPlanetaryShadows );

	oapiReadItem_bool( hFile, "Enable specular map", CFG.bSpecularMap );
	oapiReadItem_bool( hFile, "Enable normal map", CFG.bNormalMap );
	oapiReadItem_bool( hFile, "Enable height map", CFG.bHeightMap );
	oapiReadItem_bool( hFile, "Enable land cover map", CFG.bLandCoverMap );
	oapiReadItem_bool( hFile, "Enable heightmap generator", CFG.bHeightMapGenerator );

	oapiReadItem_bool( hFile, "Enable terrain self-shadowing", CFG.bTerrainShadows );
	oapiReadItem_bool( hFile, "Enable low LOD terrain features", CFG.bTerrainDetails_Low );
	oapiReadItem_bool( hFile, "Enable average LOD terrain features", CFG.bTerrainDetails_Average );
	oapiReadItem_bool( hFile, "Enable high LOD terrain features", CFG.bTerrainDetails_High );

	int idx = 0;
	sprintf( line, "ctype %d params", idx );
	while( oapiReadItem_string( hFile, line, item ) ) {
		sscanf( item, "%f %f %f", &ctp.width, &ctp.height, &ctp.max_dist );
		CFG.CTP.push_back( ctp );
		idx++;
		sprintf( line, "ctype %d params", idx );
	}
	if( !idx )	CFG.CTP.push_back( ctp );

	oapiCloseFile( hFile, FILE_IN );
}

